using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.IO;

namespace Omniture.API
{
	public class OmnitureProject : IXmlable
	{
		#region Class Members

		private readonly DateTime PivotDate = new DateTime(2000, 1, 1);

		private const bool _def_isDebug = false;

		private string _fileName = "";
		private string _configFilePath = "";
		private string _libraryFilePath = "";
		private string _scriptFilePath = "";
		private string _companyName = "";
		private TrackItemCollection _propList = null;
		private TrackItemCollection _evarList = null;
		private TrackItemCollection _eventList = null;
		private PageItemCollection _pageList = null;
		private string _projectName = "";
		private DateTime _createDate = DateTime.Now;
		private int _revisionNumber = 0;
		private string _documentKey = Guid.NewGuid().ToString();
		private bool _allowErrorOnPageNotFound = false;
		private bool _allowPageRearrange = true;
		private bool _isDebug = _def_isDebug;
		private bool _customSubmit = false;
		private string _customSubmitScript = "";
		private bool _trackDownloadLinks = false;
		private bool _trackExternalLinks = false;
		private string _linkDownloadFileTypes = "exe,zip,wav,mp3,mov,mpg,avi,doc,pdf,xls";
		private string _linkInternalFilters = "javascript:";
		private string _globalVariableName = "s";

		private int _lastObjectIndex = 0;

		#endregion

		#region Constructor

		public OmnitureProject()
		{
			_propList = new TrackItemCollection(this, TrackItemTypeConstants.PropItem);
			_evarList = new TrackItemCollection(this, TrackItemTypeConstants.EvarItem);
			_eventList = new TrackItemCollection(this, TrackItemTypeConstants.EventItem);
			_pageList = new PageItemCollection(this);
		}

		#endregion

		#region Events

		#endregion

		#region Property Implementations

		/// <summary>
		/// This is created with a document and never changes
		/// It uniquely identifies this document
		/// </summary>
		[Browsable(false)]
		public string DocumentKey
		{
			get { return _documentKey; }
		}

		/// <summary>
		/// The revision number of the project
		/// </summary>
		public int RevisionNumber
		{
			get { return _revisionNumber; }
		}

		/// <summary>
		/// Determines if an error is raised when a web page is loaded that is not in the configuration file.
		/// </summary>
		[Browsable(true)]
		[Description("Determines if an error is raised when a web page is loaded that is not in the configuration file.")]
		public bool AllowErrorOnPageNotFound
		{
			get { return _allowErrorOnPageNotFound; }
			set { _allowErrorOnPageNotFound = value; }
		}

		/// <summary>
		/// Determines if the page is rearranged before rendered for better SEO.
		/// </summary>
		[Browsable(true)]
		[Description("Determines if the page is rearranged before rendered for better SEO.")]
		public bool AllowPageRearrange
		{
			get { return _allowPageRearrange; }
			set { _allowPageRearrange = value; }
		}

		/// <summary>
		/// Determines if debug information is rendered
		/// </summary>
		[Browsable(true)]
		[Description("Determines if debug information is rendered")]
		[DefaultValue(_def_isDebug)]
		public bool IsDebug
		{
			get { return _isDebug; }
			set { _isDebug = value; }
		}

		/// <summary>
		/// Determines if the submit code is generated or custom. If true, you must build the submission routine manually.
		/// </summary>
		[Browsable(true)]
		[Description("Determines if the submit code is generated or custom. If true, you must build the submission routine manually.")]
		public bool CustomSubmit
		{
			get { return _customSubmit; }
			set { _customSubmit = value; }
		}

		/// <summary>
		/// Determines the script for a custom submit.
		/// </summary>
		[Browsable(true)]
		[Description("Determines the script for a custom submit.")]
		public string CustomSubmitScript
		{
			get { return _customSubmitScript; }
			set { _customSubmitScript = value; }
		}

		/// <summary>
		/// Determines the name of this project
		/// </summary>
		[Browsable(true)]
		[Description("Determines the name of this project")]
		public string ProjectName
		{
			get { return _projectName; }
			set { _projectName = value; }
		}

		/// <summary>
		/// Determines the name of the project's disk file
		/// </summary>
		[Browsable(true)]
		[Description("Determines the name of the project's disk file")]
		public string FileName
		{
			get { return _fileName; }
			set { _fileName = value; }
		}

		[Browsable(true)]
		public string ConfigFilePath
		{
			get { return _configFilePath; }
			set { _configFilePath = value; }
		}

		[Browsable(true)]
		public string LibraryFilePath
		{
			get { return _libraryFilePath; }
			set { _libraryFilePath = value; }
		}

		[Browsable(true)]
		public string ScriptFilePath
		{
			get { return _scriptFilePath; }
			set { _scriptFilePath = value; }
		}

		[Browsable(true)]
		public string CompanyName
		{
			get { return _companyName; }
			set { _companyName = value; }
		}

		[Browsable(false)]
		public TrackItemCollection PropList
		{
			get { return _propList; }
		}

		[Browsable(false)]
		public TrackItemCollection EVarList
		{
			get { return _evarList; }
		}

		[Browsable(false)]
		public TrackItemCollection EventList
		{
			get { return _eventList; }
		}

		[Browsable(false)]
		public PageItemCollection PageList
		{
			get { return _pageList; }
		}

		[Browsable(false)]
		internal int LastObjectIndex
		{
			get { return _lastObjectIndex; }
			set { _lastObjectIndex = value; }
		}

		[Browsable(true)]
		public bool TrackDownloadLinks
		{
			get { return _trackDownloadLinks; }
			set { _trackDownloadLinks = value; }
		}

		[Browsable(true)]
		public bool TrackExternalLinks
		{
			get { return _trackExternalLinks; }
			set { _trackExternalLinks = value; }
		}

		[Browsable(true)]
		public string LinkDownloadFileTypes
		{
			get { return _linkDownloadFileTypes; }
			set { _linkDownloadFileTypes = value; }
		}

		[Browsable(true)]
		public string LinkInternalFilters
		{
			get { return _linkInternalFilters; }
			set { _linkInternalFilters = value; }
		}

		[Browsable(true)]
		public string GlobalVariableName
		{
			get { return _globalVariableName; }
			set
			{
				if ((value == null) || (value.Trim() == ""))
					throw new Exception("Invalid value!");
				_globalVariableName = value;
			}
		}

		[Browsable(false)]
		public string LibraryName
		{
			get { return "Omniture." + TrackItemBase.CreateToken(this.CompanyName) + "." + TrackItemBase.CreateToken(this.ProjectName) + ".dll"; }
		}

		/// <summary>
		/// Determines the name of the generated XML file that the generated binary uses
		/// </summary>
		[Browsable(false)]
		[Description("Determines the name of the generated XML file that the generated binary uses")]
		public string XMLConfigFileName
		{
			get { return this.ProjectName + ".xml"; }
		}

		#endregion

		#region Methods

		public TrackItemCollection GetList(TrackItemConstants trackItemType)
		{
			switch (trackItemType)
			{
				case TrackItemConstants.EVar: return this.EVarList;
				case TrackItemConstants.Prop: return this.PropList;
				case TrackItemConstants.Event: return this.EventList;
			}
			return null;
		}

		/// <summary>
		/// Load this project from file
		/// </summary>
		/// <param name="fileName"></param>
		public void Load(string fileName)
		{
			try
			{
				XmlDocument document = new XmlDocument();
				document.Load(fileName);
				this.LoadXml(document.DocumentElement);
				this.FileName = fileName;
			}
			catch (Exception ex)
			{
				throw;
			}
		}

		/// <summary>
		/// Save this project to file
		/// </summary>
		/// <param name="fileName"></param>
		public void Save(string fileName)
		{
			try
			{
				XmlDocument document = new XmlDocument();
				document.LoadXml("<project></project>");
				this.SaveXml(document.DocumentElement);
				document.Save(fileName);
				this.FileName = fileName;
			}
			catch (Exception ex)
			{
				throw;
			}
		}

		/// <summary>
		/// Get a track object by its unique ID
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		internal ITrackItem GetTrackObject(int id)
		{
			List<ITrackItem> list = new List<ITrackItem>();
			list.AddRange(this.EVarList);
			list.AddRange(this.EventList);
			list.AddRange(this.PropList);
			foreach (ITrackItem item in list)
			{
				if (item.Id == id)
					return item;
			}
			return null;
		}

		//Get a list of validation errors for this project
		public ReadOnlyCollection<string> GetErrorList()
		{
			List<string> retval = new List<string>();

			if (TrackItemBase.CreateToken(_projectName) == "")
				retval.Add("The Project Name is not set.");

			if (TrackItemBase.CreateToken(_companyName) == "")
				retval.Add("The Company Name is not set.");

			//Check this objects values
			if (!Directory.Exists(_configFilePath))
				retval.Add("The Config File Folder does not exist");

			if (!Directory.Exists(_libraryFilePath))
				retval.Add("The Library Folder does not exist");

			if (_globalVariableName == "")
				retval.Add("The Global Script Var must be set");

			//Check the collections
			//List<ITrackItem> trackList = new List<ITrackItem>();
			//trackList.AddRange(this.EVarList.ToArray());
			//trackList.AddRange(this.EventList.ToArray());
			//trackList.AddRange(this.PropList.ToArray());

			retval.AddRange(this.ErrorCheckList(this.EVarList));
			retval.AddRange(this.ErrorCheckList(this.EventList));
			retval.AddRange(this.ErrorCheckList(this.PropList));

			//Check pages
			foreach (PageItem item in this.PageList)
			{
				if (item.PageURL == "")
					retval.Add("The page '" + item.PageName + "' must have a URL.");
			}

			return retval.AsReadOnly();
		}

		private IEnumerable<string> ErrorCheckList(IEnumerable<ITrackItem> trackList)
		{
			List<string> retval = new List<string>();
			List<string> nameCheckList = new List<string>();
			foreach (ITrackItem item in trackList)
			{
				if (!nameCheckList.Contains(item.CodeName.ToLower()))
					nameCheckList.Add(item.CodeName.ToLower());
				else
					retval.Add("There is an object of type '" + item.ItemType.ToString() + "' with a duplicate name: '" + item.CodeName + "'.");
			}
			return retval;
		}

		public ReadOnlyCollection<string> GetWarningList()
		{
			List<string> retval = new List<string>();

			if (_scriptFilePath == "")
				retval.Add("The ScriptFilePath does not exist");

			//Check pages
			foreach (PageItem item in this.PageList)
			{
				if (item.PageName == "")
					retval.Add("A page must have a name. URL: '" + item.PageURL + "'");
				else if (item.ChannelName == "")
					retval.Add("The page '" + item.PageName + "' must have a channel.");
			}

			return retval.AsReadOnly();
		}

		/// <summary>
		/// Determines if this project is valid to build
		/// </summary>
		/// <returns></returns>
		public bool Validate()
		{
			return (GetErrorList().Count == 0);
		}

		/// <summary>
		/// Given a name, this method determines if it is unqiue for all EVars and Props
		/// </summary>
		/// <param name="name">The name to check for unquieness</param>
		/// <param name="ignoreItem">A single item to ignore while checking uniqueness</param>
		/// <returns></returns>
		public bool IsTrackItemUnique(string name, ITrackItem ignoreItem)
		{
			foreach (ITrackItem item in this.EVarList)
			{
				if (string.Compare(item.Name, name, true) == 0)
					return false;
			}

			foreach (ITrackItem item in this.PropList)
			{
				if (string.Compare(item.Name, name, true) == 0)
					return false;
			}

			return true;

		}

		#endregion

		#region Generation

		/// <summary>
		/// Generate the custom assembly for this project
		/// </summary>
		/// <returns></returns>
		public virtual bool GenerateAssembly()
		{
			//Now generate the library
			string tempFolder = "";
			Omniture.API.Generators.GenerationHelper.Generate(this, false, out tempFolder);
			return true;
		}

		/// <summary>
		/// Generates the configuration XML file for this project
		/// </summary>
		public virtual bool GenerateXMLFile()
		{
			try
			{
				//Build XML Configuration file
				var fileName = Path.Combine(this.ConfigFilePath, this.XMLConfigFileName);
				if (File.Exists(fileName))
				{
					File.Delete(fileName);
					System.Threading.Thread.Sleep(500);
				}

				//Write the XML file
				var settings = new XmlWriterSettings();
				settings.Encoding = System.Text.Encoding.UTF8;
				settings.NewLineHandling = NewLineHandling.Replace;
				settings.Indent = true;
				settings.IndentChars = "\t";
				var xw = XmlTextWriter.Create(fileName, settings);
				xw.WriteStartDocument();

				xw.WriteStartElement("omnitureconfig");

				xw.WriteAttributeString("DocumentKey", this.DocumentKey);
				xw.WriteAttributeString("PublishDate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
				xw.WriteAttributeString("ProjectName", this.ProjectName);
				xw.WriteAttributeString("RevisionNumber", this.RevisionNumber.ToString());
				xw.WriteAttributeString("ScriptFilePath", this.ScriptFilePath);
				xw.WriteAttributeString("TrackDownloadLinks", this.TrackDownloadLinks.ToString());
				xw.WriteAttributeString("TrackExternalLinks", this.TrackExternalLinks.ToString());
				xw.WriteAttributeString("LinkDownloadFileTypes", this.LinkDownloadFileTypes);
				xw.WriteAttributeString("LinkInternalFilters", this.LinkInternalFilters);

				xw.WriteStartElement("pages");

				foreach (var page in this.PageList)
				{
					xw.WriteStartElement("page");
					xw.WriteAttributeString("url", page.PageURL.ToLower());
					xw.WriteAttributeString("pagename", page.PageName.ToLower());
					xw.WriteAttributeString("formname", page.FormName.ToLower());
					xw.WriteAttributeString("channel", page.ChannelName.ToLower());

					//Write the Evars
					xw.WriteStartElement("evars");
					foreach (var reference in page.EVarList)
					{
						var evar = (EVarItem)reference.TrackItem;
						xw.WriteStartElement("evar");
						xw.WriteAttributeString("alias", evar.Alias);
						xw.WriteAttributeString("datatype", evar.DataType.ToString("d"));
						xw.WriteAttributeString("name", evar.Name);
						xw.WriteAttributeString("isrequired", evar.IsRequired.ToString());
						xw.WriteAttributeString("default", evar.DefaultValue);
						xw.WriteEndElement(); //evar
					}
					xw.WriteEndElement(); //evars

					//Write the Props
					xw.WriteStartElement("props");
					foreach (var reference in page.PropList)
					{
						var prop = (PropItem)reference.TrackItem;
						xw.WriteStartElement("prop");
						xw.WriteAttributeString("alias", prop.Alias);
						xw.WriteAttributeString("datatype", prop.DataType.ToString("d"));
						xw.WriteAttributeString("name", prop.Name);
						xw.WriteAttributeString("isrequired", prop.IsRequired.ToString());
						xw.WriteAttributeString("default", prop.DefaultValue);
						xw.WriteEndElement(); //prop
					}
					xw.WriteEndElement(); //props

					//Write the Events
					xw.WriteStartElement("events");
					foreach (var reference in page.EventList)
					{
						var eItem = (EventItem)reference.TrackItem;
						xw.WriteStartElement("event");
						xw.WriteAttributeString("alias", eItem.Alias);
						xw.WriteAttributeString("name", eItem.Name);
						xw.WriteEndElement(); //event
					}
					xw.WriteEndElement(); //events

					xw.WriteEndElement(); //page
				}

				xw.WriteEndElement(); //pages
				xw.WriteEndElement(); //omnitureconfig
				xw.Close();
				return true;

			}
			catch (Exception ex)
			{
				return false;
			}
		}

		#endregion

		#region IXmlable Members

		/// <summary>
		/// Load this project from XML
		/// </summary>
		/// <param name="parentNode"></param>
		public virtual void LoadXml(XmlNode parentNode)
		{
			var ticks = XmlHelper.GetAttribute(parentNode, "CreatedDate", _createDate.Subtract(PivotDate).TotalSeconds);
			_createDate = PivotDate.AddSeconds(ticks);
			_revisionNumber = XmlHelper.GetAttribute(parentNode, "RevisionNumber", _revisionNumber);

			_documentKey = XmlHelper.GetAttribute(parentNode, "DocumentKey", _documentKey);
			_projectName = XmlHelper.GetAttribute(parentNode, "ProjectName", _projectName);
			_configFilePath = XmlHelper.GetAttribute(parentNode, "ConfigFilePath", _configFilePath);
			_libraryFilePath = XmlHelper.GetAttribute(parentNode, "LibraryFilePath", _libraryFilePath);
			_scriptFilePath = XmlHelper.GetAttribute(parentNode, "ScriptFilePath", _scriptFilePath);
			_companyName = XmlHelper.GetAttribute(parentNode, "CompanyName", _companyName);
			_allowErrorOnPageNotFound = XmlHelper.GetAttribute(parentNode, "AllowErrorOnPageNotFound", _allowErrorOnPageNotFound);
			_allowPageRearrange = XmlHelper.GetAttribute(parentNode, "AllowPageRearrange", _allowPageRearrange);
			_customSubmit = XmlHelper.GetAttribute(parentNode, "CustomSubmit", _customSubmit);
			_customSubmitScript = XmlHelper.GetAttribute(parentNode, "CustomSubmitScript", _customSubmitScript);
			_trackDownloadLinks = XmlHelper.GetAttribute(parentNode, "TrackDownloadLinks", _trackDownloadLinks);
			_trackExternalLinks = XmlHelper.GetAttribute(parentNode, "TrackExternalLinks", _trackExternalLinks);
			_linkDownloadFileTypes = XmlHelper.GetAttribute(parentNode, "LinkDownloadFileTypes", _linkDownloadFileTypes);
			_linkInternalFilters = XmlHelper.GetAttribute(parentNode, "LinkInternalFilters", _linkInternalFilters);
			_globalVariableName = XmlHelper.GetAttribute(parentNode, "GlobalVariableName", _globalVariableName);
			_isDebug = XmlHelper.GetAttribute(parentNode, "IsDebug", _def_isDebug);

			var evarNode = parentNode.SelectSingleNode("EVarList");
			this.EVarList.LoadXml(evarNode);

			var eventNode = parentNode.SelectSingleNode("EventList");
			this.EventList.LoadXml(eventNode);

			var propNode = parentNode.SelectSingleNode("PropList");
			this.PropList.LoadXml(propNode);

			var pageNode = parentNode.SelectSingleNode("PageList");
			this.PageList.LoadXml(pageNode);

			//Get highest ID
			List<ITrackItem> list = new List<ITrackItem>();
			list.AddRange(this.EVarList);
			list.AddRange(this.EventList);
			list.AddRange(this.PropList);
			this.LastObjectIndex = 0;
			foreach (ITrackItem item in list)
			{
				if (this.LastObjectIndex < item.Id)
					this.LastObjectIndex = item.Id;
			}

		}

		/// <summary>
		/// Save this project to XML
		/// </summary>
		/// <param name="parentNode"></param>
		public virtual void SaveXml(XmlNode parentNode)
		{
			//Add the connection
			XmlHelper.AddAttribute(parentNode, "DocumentKey", this.DocumentKey);
			XmlHelper.AddAttribute(parentNode, "CreatedDate", _createDate.Subtract(PivotDate).TotalSeconds);
			XmlHelper.AddAttribute(parentNode, "ProjectName", _projectName);
			XmlHelper.AddAttribute(parentNode, "ConfigFilePath", _configFilePath);
			XmlHelper.AddAttribute(parentNode, "LibraryFilePath", _libraryFilePath);
			XmlHelper.AddAttribute(parentNode, "ScriptFilePath", _scriptFilePath);
			XmlHelper.AddAttribute(parentNode, "CompanyName", _companyName);
			XmlHelper.AddAttribute(parentNode, "AllowErrorOnPageNotFound", _allowErrorOnPageNotFound);
			XmlHelper.AddAttribute(parentNode, "AllowPageRearrange", _allowPageRearrange);
			XmlHelper.AddAttribute(parentNode, "CustomSubmit", _customSubmit);
			XmlHelper.AddAttribute(parentNode, "CustomSubmitScript", _customSubmitScript);
			XmlHelper.AddAttribute(parentNode, "TrackDownloadLinks", _trackDownloadLinks);
			XmlHelper.AddAttribute(parentNode, "TrackExternalLinks", _trackExternalLinks);
			XmlHelper.AddAttribute(parentNode, "LinkDownloadFileTypes", _linkDownloadFileTypes);
			XmlHelper.AddAttribute(parentNode, "LinkInternalFilters", _linkInternalFilters);
			XmlHelper.AddAttribute(parentNode, "GlobalVariableName", _globalVariableName);
			XmlHelper.AddAttribute(parentNode, "IsDebug", _isDebug);

			_revisionNumber++;
			XmlHelper.AddAttribute(parentNode, "RevisionNumber", _revisionNumber);

			//Save the collections
			this.EVarList.SaveXml(XmlHelper.AddNode(parentNode, "EVarList"));
			this.EventList.SaveXml(XmlHelper.AddNode(parentNode, "EventList"));
			this.PropList.SaveXml(XmlHelper.AddNode(parentNode, "PropList"));
			this.PageList.SaveXml(XmlHelper.AddNode(parentNode, "PageList"));
		}

		#endregion

	}
}